/*-------------------------------------------------------------------------
 *
 * file_fdw.c
 *		  用于服务器端平面文件（或程序）的外部数据包装器。
 *
 * Copyright (c) 2010-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		  contrib/file_fdw/file_fdw.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <sys/stat.h>
#include <unistd.h>

#include "access/htup_details.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_foreign_table.h"
#include "commands/copy.h"
#include "commands/defrem.h"
#include "commands/explain.h"
#include "commands/vacuum.h"
#include "foreign/fdwapi.h"
#include "foreign/foreign.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/planmain.h"
#include "optimizer/restrictinfo.h"
#include "utils/acl.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/sampling.h"

PG_MODULE_MAGIC;

/*
 * 描述使用此包装器的对象的有效选项。
 */
struct FileFdwOption
{
	const char *optname;
	Oid			optcontext;		/* 选项可能出现的目录的 Oid */
};

/*
 * file_fdw 的有效选项。
 * 这些选项基于 COPY FROM 命令的选项。
 * 但请注意，force_not_null 和 force_null 被视为附加到列的布尔选项，而不是表选项。
 *
 * 注意：如果您要为用户映射添加新选项，则需要修改
 * fileGetOptions()，该函数当前不会考虑用户映射。
 */
static const struct FileFdwOption valid_options[] = {
	/* 数据源选项 */
	{"filename", ForeignTableRelationId},
	{"program", ForeignTableRelationId},

	/* 格式选项 */
	/* oids 选项不受支持 */
	{"format", ForeignTableRelationId},
	{"header", ForeignTableRelationId},
	{"delimiter", ForeignTableRelationId},
	{"quote", ForeignTableRelationId},
	{"escape", ForeignTableRelationId},
	{"null", ForeignTableRelationId},
	{"encoding", ForeignTableRelationId},
	{"force_not_null", AttributeRelationId},
	{"force_null", AttributeRelationId},

	/*
	 * file_fdw 不支持 force_quote，因为它用于 COPY TO。
	 */

	/* 哨兵 */
	{NULL, InvalidOid}
};

/*
 * RelOptInfo.fdw_private 的 FDW 特定信息。
 */
typedef struct FileFdwPlanState
{
	char	   *filename;		/* 要读取的文件或程序 */
	bool		is_program;		/* 如果文件名表示操作系统命令，则为真 */
	List	   *options;		/* 合并的 COPY 选项，不包括文件名和
								 * is_program */
	BlockNumber pages;			/* 文件的物理大小估计 */
	double		ntuples;		/* 数据行数的估计 */
} FileFdwPlanState;

/*
 * ForeignScanState.fdw_state 的 FDW 特定信息。
 */
typedef struct FileFdwExecutionState
{
	char	   *filename;		/* 要读取的文件或程序 */
	bool		is_program;		/* 如果文件名表示操作系统命令，则为真 */
	List	   *options;		/* 合并的 COPY 选项，不包括文件名和
								 * is_program */
	CopyFromState cstate;		/* COPY 执行状态 */
} FileFdwExecutionState;

/*
 * SQL 函数
 */
PG_FUNCTION_INFO_V1(file_fdw_handler);
PG_FUNCTION_INFO_V1(file_fdw_validator);

/*
 * FDW 回调例程
 */
static void fc_fileGetForeignRelSize(PlannerInfo *fc_root,
								  RelOptInfo *fc_baserel,
								  Oid fc_foreigntableid);
static void fc_fileGetForeignPaths(PlannerInfo *fc_root,
								RelOptInfo *fc_baserel,
								Oid fc_foreigntableid);
static ForeignScan *fc_fileGetForeignPlan(PlannerInfo *fc_root,
									   RelOptInfo *fc_baserel,
									   Oid fc_foreigntableid,
									   ForeignPath *fc_best_path,
									   List *fc_tlist,
									   List *fc_scan_clauses,
									   Plan *fc_outer_plan);
static void fc_fileExplainForeignScan(ForeignScanState *fc_node, ExplainState *fc_es);
static void fc_fileBeginForeignScan(ForeignScanState *fc_node, int fc_eflags);
static TupleTableSlot *fc_fileIterateForeignScan(ForeignScanState *fc_node);
static void fc_fileReScanForeignScan(ForeignScanState *fc_node);
static void fc_fileEndForeignScan(ForeignScanState *fc_node);
static bool fc_fileAnalyzeForeignTable(Relation fc_relation,
									AcquireSampleRowsFunc *fc_func,
									BlockNumber *fc_totalpages);
static bool fc_fileIsForeignScanParallelSafe(PlannerInfo *fc_root, RelOptInfo *fc_rel,
										  RangeTblEntry *fc_rte);

/*
 * 辅助函数
 */
static bool fc_is_valid_option(const char *fc_option, Oid fc_context);
static void fc_fileGetOptions(Oid fc_foreigntableid,
						   char **fc_filename,
						   bool *fc_is_program,
						   List **fc_other_options);
static List *fc_get_file_fdw_attribute_options(Oid fc_relid);
static bool fc_check_selective_binary_conversion(RelOptInfo *fc_baserel,
											  Oid fc_foreigntableid,
											  List **fc_columns);
static void fc_estimate_size(PlannerInfo *fc_root, RelOptInfo *fc_baserel,
						  FileFdwPlanState *fc_fdw_private);
static void fc_estimate_costs(PlannerInfo *fc_root, RelOptInfo *fc_baserel,
						   FileFdwPlanState *fc_fdw_private,
						   Cost *fc_startup_cost, Cost *fc_total_cost);
static int	fc_file_acquire_sample_rows(Relation fc_onerel, int fc_elevel,
									 HeapTuple *fc_rows, int fc_targrows,
									 double *fc_totalrows, double *fc_totaldeadrows);


/*
 * 外部数据包装器处理函数：返回一个结构，包含指向
 * 我的回调例程的指针。
 */
Datum file_fdw_handler(PG_FUNCTION_ARGS)
{
	FdwRoutine *fc_fdwroutine = makeNode(FdwRoutine);

	fc_fdwroutine->GetForeignRelSize = fc_fileGetForeignRelSize;
	fc_fdwroutine->GetForeignPaths = fc_fileGetForeignPaths;
	fc_fdwroutine->GetForeignPlan = fc_fileGetForeignPlan;
	fc_fdwroutine->ExplainForeignScan = fc_fileExplainForeignScan;
	fc_fdwroutine->BeginForeignScan = fc_fileBeginForeignScan;
	fc_fdwroutine->IterateForeignScan = fc_fileIterateForeignScan;
	fc_fdwroutine->ReScanForeignScan = fc_fileReScanForeignScan;
	fc_fdwroutine->EndForeignScan = fc_fileEndForeignScan;
	fc_fdwroutine->AnalyzeForeignTable = fc_fileAnalyzeForeignTable;
	fc_fdwroutine->IsForeignScanParallelSafe = fc_fileIsForeignScanParallelSafe;

	PG_RETURN_POINTER(fc_fdwroutine);
}

/*
 * 验证提供给使用 file_fdw 的 FOREIGN DATA WRAPPER、SERVER、
 * USER MAPPING 或 FOREIGN TABLE 的通用选项。
 *
 * 如果选项或其值被认为无效，则引发错误。
 */
Datum file_fdw_validator(PG_FUNCTION_ARGS)
{
	List	   *fc_options_list = untransformRelOptions(PG_GETARG_DATUM(0));
	Oid			fc_catalog = PG_GETARG_OID(1);
	char	   *fc_filename = NULL;
	DefElem    *fc_force_not_null = NULL;
	DefElem    *fc_force_null = NULL;
	List	   *fc_other_options = NIL;
	ListCell   *fc_cell;

	/*
	 * 检查只提供 file_fdw 支持的选项，并且当前对象类型允许的选项。
	 */
	foreach(fc_cell, fc_options_list)
	{
		DefElem    *fc_def = (DefElem *) lfirst(fc_cell);

		if (!fc_is_valid_option(fc_def->defname, fc_catalog))
		{
			const struct FileFdwOption *fc_opt;
			StringInfoData fc_buf;

			/*
			 * 指定了未知选项，进行投诉。提供与该对象有效选项列表的提示。
			 */
			initStringInfo(&fc_buf);
			for (fc_opt = valid_options; fc_opt->optname; fc_opt++)
			{
				if (fc_catalog == fc_opt->optcontext)
					appendStringInfo(&fc_buf, "%s%s", (fc_buf.len > 0) ? ", " : "",
									 fc_opt->optname);
			}

			ereport(ERROR,
					(errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
					 errmsg("invalid option \"%s\"", fc_def->defname),
					 fc_buf.len > 0
					 ? errhint("Valid options in this context are: %s",
							   fc_buf.data)
					 : errhint("There are no valid options in this context.")));
		}

		/*
		 * 将文件名、程序和特定于列的选项分开，因为
		 * ProcessCopyOptions 不会接受它们。
		 */
		if (strcmp(fc_def->defname, "filename") == 0 ||
			strcmp(fc_def->defname, "program") == 0)
		{
			if (fc_filename)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("conflicting or redundant options")));

			/*
			 * 检查更改 file_fdw 使用的文件或程序的权限。
			 *
			 * 只有 'pg_read_server_files' 角色的成员可以
			 * 设置 file_fdw 外部表的 'filename' 选项，而
			 * 只有 'pg_execute_server_program' 角色的成员可以
			 * 设置 'program' 选项。这是因为我们不希望普通用户
			 * 能够控制读取哪个文件或执行哪个程序。
			 *
			 * 将这种权限检查放在验证器中有点不妥，但似乎没有
			 * 其他地方可以更干净地执行检查。
			 *
			 * 请注意，valid_options[] 数组不允许在除外部表之外的任何选项级别设置文件名
			 * 和程序——否则仍然会存在安全漏洞。
			 */
			if (strcmp(fc_def->defname, "filename") == 0 &&
				!has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
				ereport(ERROR,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));

			if (strcmp(fc_def->defname, "program") == 0 &&
				!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
				ereport(ERROR,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("only superuser or a role with privileges of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));

			fc_filename = defGetString(fc_def);
		}

		/*
		 * force_not_null是一个布尔选项；在验证后我们可以丢弃它 - 它将在get_file_fdw_attribute_options()中稍后检索
		 */
		else if (strcmp(fc_def->defname, "force_not_null") == 0)
		{
			if (fc_force_not_null)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("conflicting or redundant options"),
						 errhint("Option \"force_not_null\" supplied more than once for a column.")));
			fc_force_not_null = fc_def;
			/* 不关心值是什么，只要它是一个合法的布尔值 */
			(void) defGetBoolean(fc_def);
		}
		/* 请参阅上面的force_not_null的注释 */
		else if (strcmp(fc_def->defname, "force_null") == 0)
		{
			if (fc_force_null)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("conflicting or redundant options"),
						 errhint("Option \"force_null\" supplied more than once for a column.")));
			fc_force_null = fc_def;
			(void) defGetBoolean(fc_def);
		}
		else
			fc_other_options = lappend(fc_other_options, fc_def);
	}

	/*
	 * 现在应用核心COPY代码的验证逻辑进行更多检查。
	 */
	ProcessCopyOptions(NULL, NULL, true, fc_other_options);

	/*
	 * 对于file_fdw外部表，文件名或程序选项是必需的。
	 */
	if (fc_catalog == ForeignTableRelationId && fc_filename == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED),
				 errmsg("either filename or program is required for file_fdw foreign tables")));

	PG_RETURN_VOID();
}

/*
 * 检查提供的选项是否是有效选项之一。
 * context是选项所对应对象的目录的Oid。
 */
static bool fc_is_valid_option(const char *fc_option, Oid fc_context)
{
	const struct FileFdwOption *fc_opt;

	for (fc_opt = valid_options; fc_opt->optname; fc_opt++)
	{
		if (fc_context == fc_opt->optcontext && strcmp(fc_opt->optname, fc_option) == 0)
			return true;
	}
	return false;
}

/*
 * 获取file_fdw外部表的选项。
 *
 * 我们必须将文件名/程序与其他选项分开，因为这些不能出现在传递给核心COPY代码的选项列表中。
 */
static void fc_fileGetOptions(Oid fc_foreigntableid,
			   char **fc_filename, bool *fc_is_program, List **fc_other_options)
{
	ForeignTable *fc_table;
	ForeignServer *fc_server;
	ForeignDataWrapper *fc_wrapper;
	List	   *fc_options;
	ListCell   *fc_lc;

	/*
	 * 从FDW对象中提取选项。我们忽略用户映射，因为
	 * file_fdw没有可以在其中指定的选项。
	 *
	 * (XXX 实际上，考虑到当前valid_options[]的内容，查看外部表自己的选项以外的内容没有意义。
	 * 简化一下？)
	 */
	fc_table = GetForeignTable(fc_foreigntableid);
	fc_server = GetForeignServer(fc_table->serverid);
	fc_wrapper = GetForeignDataWrapper(fc_server->fdwid);

	fc_options = NIL;
	fc_options = list_concat(fc_options, fc_wrapper->options);
	fc_options = list_concat(fc_options, fc_server->options);
	fc_options = list_concat(fc_options, fc_table->options);
	fc_options = list_concat(fc_options, fc_get_file_fdw_attribute_options(fc_foreigntableid));

	/*
	 * 分离出文件名或程序选项（我们假设只有一个）。
	 */
	*fc_filename = NULL;
	*fc_is_program = false;
	foreach(fc_lc, fc_options)
	{
		DefElem    *fc_def = (DefElem *) lfirst(fc_lc);

		if (strcmp(fc_def->defname, "filename") == 0)
		{
			*fc_filename = defGetString(fc_def);
			fc_options = foreach_delete_current(fc_options, fc_lc);
			break;
		}
		else if (strcmp(fc_def->defname, "program") == 0)
		{
			*fc_filename = defGetString(fc_def);
			*fc_is_program = true;
			fc_options = foreach_delete_current(fc_options, fc_lc);
			break;
		}
	}

	/*
	 * 验证器应该检查文件名或程序是否包含在选项中，但再检查一次，以防万一。
	 */
	if (*fc_filename == NULL)
		elog(ERROR, "either filename or program is required for file_fdw foreign tables");

	*fc_other_options = fc_options;
}

/*
 * 从pg_attribute中检索每列的通用选项，并构建一个表示它们的DefElems列表。
 *
 * 目前我们只有 "force_not_null" 和 "force_null"，这两个选项应合并为一个DefElem，列出所有这样的列，因为这正是COPY所期望的。
 */
static List * fc_get_file_fdw_attribute_options(Oid fc_relid)
{
	Relation	fc_rel;
	TupleDesc	fc_tupleDesc;
	AttrNumber	fc_natts;
	AttrNumber	fc_attnum;
	List	   *fc_fnncolumns = NIL;
	List	   *fc_fncolumns = NIL;

	List	   *fc_options = NIL;

	fc_rel = table_open(fc_relid, AccessShareLock);
	fc_tupleDesc = RelationGetDescr(fc_rel);
	fc_natts = fc_tupleDesc->natts;

	/* 为所有用户定义的属性检索FDW选项。 */
	for (fc_attnum = 1; fc_attnum <= fc_natts; fc_attnum++)
	{
		Form_pg_attribute fc_attr = TupleDescAttr(fc_tupleDesc, fc_attnum - 1);
		List	   *fc_options;
		ListCell   *fc_lc;

		/* 跳过已删除的属性。 */
		if (fc_attr->attisdropped)
			continue;

		fc_options = GetForeignColumnOptions(fc_relid, fc_attnum);
		foreach(fc_lc, fc_options)
		{
			DefElem    *fc_def = (DefElem *) lfirst(fc_lc);

			if (strcmp(fc_def->defname, "force_not_null") == 0)
			{
				if (defGetBoolean(fc_def))
				{
					char	   *fc_attname = pstrdup(NameStr(fc_attr->attname));

					fc_fnncolumns = lappend(fc_fnncolumns, makeString(fc_attname));
				}
			}
			else if (strcmp(fc_def->defname, "force_null") == 0)
			{
				if (defGetBoolean(fc_def))
				{
					char	   *fc_attname = pstrdup(NameStr(fc_attr->attname));

					fc_fncolumns = lappend(fc_fncolumns, makeString(fc_attname));
				}
			}
			/* 也许将来在这里处理其他选项 */
		}
	}

	table_close(fc_rel, AccessShareLock);

	/*
	 * 仅当某些列设置了force_not_null / force_null选项时返回DefElem
	 */
	if (fc_fnncolumns != NIL)
		fc_options = lappend(fc_options, makeDefElem("force_not_null", (Node *) fc_fnncolumns, -1));

	if (fc_fncolumns != NIL)
		fc_options = lappend(fc_options, makeDefElem("force_null", (Node *) fc_fncolumns, -1));

	return fc_options;
}

/*
 * fileGetForeignRelSize
 *		获取外部表的关系大小估计
 */
static void fc_fileGetForeignRelSize(PlannerInfo *fc_root,
					  RelOptInfo *fc_baserel,
					  Oid fc_foreigntableid)
{
	FileFdwPlanState *fc_fdw_private;

	/*
	 * 获取选项。在这一点上我们只需要文件名（或程序），但
	 * 我们不妨把所有内容都获取，以免将来在规划中重新获取。
	 */
	fc_fdw_private = (FileFdwPlanState *) palloc(sizeof(FileFdwPlanState));
	fc_fileGetOptions(fc_foreigntableid,
				   &fc_fdw_private->filename,
				   &fc_fdw_private->is_program,
				   &fc_fdw_private->options);
	fc_baserel->fdw_private = (void *) fc_fdw_private;

	/* 估计关系大小 */
	fc_estimate_size(fc_root, fc_baserel, fc_fdw_private);
}

/*
 * fileGetForeignPaths
 *		为外部表上的扫描创建可能的访问路径
 *
 *		目前我们不支持任何下推特性，所以只有一种
 *		可能的访问路径，这条路径简单地按数据文件中的顺序返回所有记录。
 */
static void fc_fileGetForeignPaths(PlannerInfo *fc_root,
					RelOptInfo *fc_baserel,
					Oid fc_foreigntableid)
{
	FileFdwPlanState *fc_fdw_private = (FileFdwPlanState *) fc_baserel->fdw_private;
	Cost		fc_startup_cost;
	Cost		fc_total_cost;
	List	   *fc_columns;
	List	   *fc_coptions = NIL;

	/* 决定是否有选择性地进行二进制转换 */
	if (fc_check_selective_binary_conversion(fc_baserel,
										  fc_foreigntableid,
										  &fc_columns))
		fc_coptions = list_make1(makeDefElem("convert_selectively",
										  (Node *) fc_columns, -1));

	/* 估计成本 */
	fc_estimate_costs(fc_root, fc_baserel, fc_fdw_private,
				   &fc_startup_cost, &fc_total_cost);

	/*
	 * 创建一个 ForeignPath 节点并将其添加为唯一可能的路径。我们使用
	 * fdw_private 列表来承载 convert_selectively 选项；
	 * 该选项将传播到 Plan 节点的 fdw_private 列表中。
	 *
	 * 我们不支持将连接子句推入该路径的 quals 中，但由于 LATERAL 引用
	 * 在其 tlist 中，它仍然可能有所需的参数化。
	 */
	add_path(fc_baserel, (Path *)
			 create_foreignscan_path(fc_root, fc_baserel,
									 NULL,	/* 默认的 pathtarget */
									 fc_baserel->rows,
									 fc_startup_cost,
									 fc_total_cost,
									 NIL,	/* 无 pathkeys */
									 fc_baserel->lateral_relids,
									 NULL,	/* 无额外计划 */
									 fc_coptions));

	/*
	 * 如果数据文件已排序，并且我们以某种方式知道这一点，我们可以插入
	 * 适当的 pathkeys 到 ForeignPath 节点，以告知规划器。
	 */
}

/*
 * fileGetForeignPlan
 *		为扫描外部表创建一个 ForeignScan 计划节点
 */
static ForeignScan * fc_fileGetForeignPlan(PlannerInfo *fc_root,
				   RelOptInfo *fc_baserel,
				   Oid fc_foreigntableid,
				   ForeignPath *fc_best_path,
				   List *fc_tlist,
				   List *fc_scan_clauses,
				   Plan *fc_outer_plan)
{
	Index		fc_scan_relid = fc_baserel->relid;

	/*
	 * 我们没有原生能力来评估限制子句，因此我们只是将所有 scan_clauses
	 * 放入计划节点的 qual 列表中供执行器检查。因此我们在这里要做的
	 * 就是从子句中剥离 RestrictInfo 节点并忽略伪常量（将由其他地方处理）。
	 */
	fc_scan_clauses = extract_actual_clauses(fc_scan_clauses, false);

	/* 创建 ForeignScan 节点 */
	return make_foreignscan(fc_tlist,
							fc_scan_clauses,
							fc_scan_relid,
							NIL,	/* 无需评估的表达式 */
							fc_best_path->fdw_private,
							NIL,	/* 无自定义 tlist */
							NIL,	/* 无远程 quals */
							fc_outer_plan);
}

/*
 * fileExplainForeignScan
 *		为 EXPLAIN 生成额外输出
 */
static void fc_fileExplainForeignScan(ForeignScanState *fc_node, ExplainState *fc_es)
{
	char	   *fc_filename;
	bool		fc_is_program;
	List	   *fc_options;

	/* 获取选项 --- 此时我们只需要文件名和 is_program */
	fc_fileGetOptions(RelationGetRelid(fc_node->ss.ss_currentRelation),
				   &fc_filename, &fc_is_program, &fc_options);

	if (fc_is_program)
		ExplainPropertyText("Foreign Program", fc_filename, fc_es);
	else
		ExplainPropertyText("Foreign File", fc_filename, fc_es);

	/* 如果我们不显示成本细节，则抑制文件大小 */
	if (fc_es->costs)
	{
		struct stat fc_stat_buf;

		if (!fc_is_program &&
			stat(fc_filename, &fc_stat_buf) == 0)
			ExplainPropertyInteger("Foreign File Size", "b",
								   (int64) fc_stat_buf.st_size, fc_es);
	}
}

/*
 * fileBeginForeignScan
 *		通过创建 CopyState 启动对文件的访问
 */
static void fc_fileBeginForeignScan(ForeignScanState *fc_node, int fc_eflags)
{
	ForeignScan *fc_plan = (ForeignScan *) fc_node->ss.ps.plan;
	char	   *fc_filename;
	bool		fc_is_program;
	List	   *fc_options;
	CopyFromState fc_cstate;
	FileFdwExecutionState *fc_festate;

	/*
	 * 在 EXPLAIN（无 ANALYZE）情况下不执行任何操作。node->fdw_state 保持 NULL。
	 */
	if (fc_eflags & EXEC_FLAG_EXPLAIN_ONLY)
		return;

	/* 获取外部表的选项 */
	fc_fileGetOptions(RelationGetRelid(fc_node->ss.ss_currentRelation),
				   &fc_filename, &fc_is_program, &fc_options);

	/* 添加计划中的任何选项（目前仅为 convert_selectively） */
	fc_options = list_concat(fc_options, fc_plan->fdw_private);

	/*
	 * 从 FDW 选项创建 CopyState。我们总是获取所有列，
	 * 以匹配预期的 ScanTupleSlot 签名。
	 */
	fc_cstate = BeginCopyFrom(NULL,
						   fc_node->ss.ss_currentRelation,
						   NULL,
						   fc_filename,
						   fc_is_program,
						   NULL,
						   NIL,
						   fc_options);

	/*
	 * 将状态保存在 node->fdw_state 中。我们必须保存足够的信息以重新调用
	 * BeginCopyFrom()。
	 */
	fc_festate = (FileFdwExecutionState *) palloc(sizeof(FileFdwExecutionState));
	fc_festate->filename = fc_filename;
	fc_festate->is_program = fc_is_program;
	fc_festate->options = fc_options;
	fc_festate->cstate = fc_cstate;

	fc_node->fdw_state = (void *) fc_festate;
}

/*
 * fileIterateForeignScan
 *		从数据文件中读取下一个记录并将其存储到
 *		ScanTupleSlot 作为虚拟元组
 */
static TupleTableSlot * fc_fileIterateForeignScan(ForeignScanState *fc_node)
{
	FileFdwExecutionState *fc_festate = (FileFdwExecutionState *) fc_node->fdw_state;
	TupleTableSlot *fc_slot = fc_node->ss.ss_ScanTupleSlot;
	bool		fc_found;
	ErrorContextCallback fc_errcallback;

	/* 设置回调以识别错误行号。 */
	fc_errcallback.callback = CopyFromErrorCallback;
	fc_errcallback.arg = (void *) fc_festate->cstate;
	fc_errcallback.previous = error_context_stack;
	error_context_stack = &fc_errcallback;

	/*
	 * 将虚拟元组加载到插槽中的协议是首先
	 * ExecClearTuple，然后填充值/isnull 数组，然后
	 * ExecStoreVirtualTuple。如果我们在文件中没有找到其他行，
	 * 我们只需跳过最后一步，保持插槽为空，这是必需的。
	 *
	 * 我们可以将 ExprContext 设为 NULL，因为我们从文件中读取了所有列，
	 * 所以无需评估默认表达式。
	 */
	ExecClearTuple(fc_slot);
	fc_found = NextCopyFrom(fc_festate->cstate, NULL,
						 fc_slot->tts_values, fc_slot->tts_isnull);
	if (fc_found)
		ExecStoreVirtualTuple(fc_slot);

	/* 移除错误回调。 */
	error_context_stack = fc_errcallback.previous;

	return fc_slot;
}

/*
 * fileReScanForeignScan
 *		重新扫描表，可能带有新参数
 */
static void fc_fileReScanForeignScan(ForeignScanState *fc_node)
{
	FileFdwExecutionState *fc_festate = (FileFdwExecutionState *) fc_node->fdw_state;

	EndCopyFrom(fc_festate->cstate);

	fc_festate->cstate = BeginCopyFrom(NULL,
									fc_node->ss.ss_currentRelation,
									NULL,
									fc_festate->filename,
									fc_festate->is_program,
									NULL,
									NIL,
									fc_festate->options);
}

/*
 * fileEndForeignScan
 *		完成对外部表的扫描并处理用于此扫描的对象
 */
static void fc_fileEndForeignScan(ForeignScanState *fc_node)
{
	FileFdwExecutionState *fc_festate = (FileFdwExecutionState *) fc_node->fdw_state;

	/* 如果 festate 为 NULL，我们处于 EXPLAIN；无事可做 */
	if (fc_festate)
		EndCopyFrom(fc_festate->cstate);
}

/*
 * fileAnalyzeForeignTable
 *		测试是否支持分析该外部表
 */
static bool fc_fileAnalyzeForeignTable(Relation fc_relation,
						AcquireSampleRowsFunc *fc_func,
						BlockNumber *fc_totalpages)
{
	char	   *fc_filename;
	bool		fc_is_program;
	List	   *fc_options;
	struct stat fc_stat_buf;

	/* 获取外部表的选项 */
	fc_fileGetOptions(RelationGetRelid(fc_relation), &fc_filename, &fc_is_program, &fc_options);

	/*
	 * 如果这是一个程序而不是文件，只需返回 false 来跳过
	 * 分析表。我们可以运行程序并收集其当前返回的统计数据，
	 * 但在这种情况下输出可能过于波动，无法用作有用的统计数据。
	 * 也许应该有一个选项来启用这样做？
	 */
	if (fc_is_program)
		return false;

	/*
	 * 获取文件的大小。 (XXX 如果我们在这里失败，直接
	 * 返回 false 来跳过分析表会不会更好？)
	 */
	if (stat(fc_filename, &fc_stat_buf) < 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not stat file \"%s\": %m",
						fc_filename)));

	/*
	 * 将大小转换为页。 必须至少返回 1，以便我们稍后
	 * 判断 pg_class.relpages 不是默认值。
	 */
	*fc_totalpages = (fc_stat_buf.st_size + (BLCKSZ - 1)) / BLCKSZ;
	if (*fc_totalpages < 1)
		*fc_totalpages = 1;

	*fc_func = fc_file_acquire_sample_rows;

	return true;
}

/*
 * fileIsForeignScanParallelSafe
 *		在并行工作者中读取文件或外部程序应该与
 *		在领导者中读取一样有效，因此标记扫描为安全。
 */
static bool fc_fileIsForeignScanParallelSafe(PlannerInfo *fc_root, RelOptInfo *fc_rel,
							  RangeTblEntry *fc_rte)
{
	return true;
}

/*
 * check_selective_binary_conversion
 *
 * 检查仅转换文件某些列为二进制是否有用。 如果是，
 * 构建要转换的列名列表，返回到 *columns，并返回 true。
 * (注意，可能会确定不需要转换任何列，例如使用 COUNT(*)
 * 查询。因此，我们不能使用返回一个 NIL 列表来表示失败。)
 */
static bool fc_check_selective_binary_conversion(RelOptInfo *fc_baserel,
								  Oid fc_foreigntableid,
								  List **fc_columns)
{
	ForeignTable *fc_table;
	ListCell   *fc_lc;
	Relation	fc_rel;
	TupleDesc	fc_tupleDesc;
	AttrNumber	fc_attnum;
	Bitmapset  *fc_attrs_used = NULL;
	bool		fc_has_wholerow = false;
	int			fc_numattrs;
	int			fc_i;

	*fc_columns = NIL;				/* 默认结果 */

	/*
	 * 检查文件格式。 如果是二进制格式，这个无关紧要。
	 */
	fc_table = GetForeignTable(fc_foreigntableid);
	foreach(fc_lc, fc_table->options)
	{
		DefElem    *fc_def = (DefElem *) lfirst(fc_lc);

		if (strcmp(fc_def->defname, "format") == 0)
		{
			char	   *fc_format = defGetString(fc_def);

			if (strcmp(fc_format, "binary") == 0)
				return false;
			break;
		}
	}

	/* 收集进行连接或最终输出所需的所有属性。 */
	pull_varattnos((Node *) fc_baserel->reltarget->exprs, fc_baserel->relid,
				   &fc_attrs_used);

	/* 添加所有在限制子句中使用的属性。 */
	foreach(fc_lc, fc_baserel->baserestrictinfo)
	{
		RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_lc);

		pull_varattnos((Node *) fc_rinfo->clause, fc_baserel->relid,
					   &fc_attrs_used);
	}

	/* 将属性编号转换为列名。 */
	fc_rel = table_open(fc_foreigntableid, AccessShareLock);
	fc_tupleDesc = RelationGetDescr(fc_rel);

	while ((fc_attnum = bms_first_member(fc_attrs_used)) >= 0)
	{
		/* 调整系统属性。 */
		fc_attnum += FirstLowInvalidHeapAttributeNumber;

		if (fc_attnum == 0)
		{
			fc_has_wholerow = true;
			break;
		}

		/* 忽略系统属性。 */
		if (fc_attnum < 0)
			continue;

		/* 获取用户属性。 */
		if (fc_attnum > 0)
		{
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupleDesc, fc_attnum - 1);
			char	   *fc_attname = NameStr(fc_attr->attname);

			/* 跳过已删除的属性（这里可能不应该看到任何）。 */
			if (fc_attr->attisdropped)
				continue;

			/*
			 * 跳过生成的列（COPY 不会在列
			 * 列表中接受它们）
			 */
			if (fc_attr->attgenerated)
				continue;
			*fc_columns = lappend(*fc_columns, makeString(pstrdup(fc_attname)));
		}
	}

	/* 计算未删除的用户属性，当我们有 tupdesc 时。 */
	fc_numattrs = 0;
	for (fc_i = 0; fc_i < fc_tupleDesc->natts; fc_i++)
	{
		Form_pg_attribute fc_attr = TupleDescAttr(fc_tupleDesc, fc_i);

		if (fc_attr->attisdropped)
			continue;
		fc_numattrs++;
	}

	table_close(fc_rel, AccessShareLock);

	/* 如果有整个行的引用，则失败：我们需要所有列。 */
	if (fc_has_wholerow)
	{
		*fc_columns = NIL;
		return false;
	}

	/* 如果需要所有用户属性，则失败。 */
	if (fc_numattrs == list_length(*fc_columns))
	{
		*fc_columns = NIL;
		return false;
	}

	return true;
}

/*
 * 估算外部表的大小。
 *
 * 主要结果返回在 baserel->rows 中。我们还设置
 * fdw_private->pages 和 fdw_private->ntuples 以供后续的成本
 * 计算使用。
 */
static void fc_estimate_size(PlannerInfo *fc_root, RelOptInfo *fc_baserel,
			  FileFdwPlanState *fc_fdw_private)
{
	struct stat fc_stat_buf;
	BlockNumber fc_pages;
	double		fc_ntuples;
	double		fc_nrows;

	/*
	 * 获取文件的大小。 但是在计划时可能不存在，
	 * 在这种情况下我们不得不使用默认估计。如果使用程序作为输入，
	 * 我们也必须退回到默认值。
	 */
	if (fc_fdw_private->is_program || stat(fc_fdw_private->filename, &fc_stat_buf) < 0)
		fc_stat_buf.st_size = 10 * BLCKSZ;

	/*
	 * 将大小转换为页，以便在稍后的 I/O 成本估算中使用。
	 */
	fc_pages = (fc_stat_buf.st_size + (BLCKSZ - 1)) / BLCKSZ;
	if (fc_pages < 1)
		fc_pages = 1;
	fc_fdw_private->pages = fc_pages;

	/*
	 * 估算文件中的元组数量。
	 */
	if (fc_baserel->tuples >= 0 && fc_baserel->pages > 0)
	{
		/*
		 * 我们有来自 pg_class 的页数和元组数量（即，
		 * 来自之前的 ANALYZE），因此计算每页的元组估计，
		 * 并按当前文件大小缩放。
		 */
		double		fc_density;

		fc_density = fc_baserel->tuples / (double) fc_baserel->pages;
		fc_ntuples = clamp_row_est(fc_density * (double) fc_pages);
	}
	else
	{
		/*
		 * 否则我们必须伪造。我们使用规划者对关系宽度的想法回退到这个估计；如果并不是所有列都被读取，这就是虚假的，更不用说行的文本表示可能与其内部表示的大小不同。可能我们可以做得更好，但是对任何抱怨的人真正的回答是“ANALYZE”...
		 */
		int			fc_tuple_width;

		fc_tuple_width = MAXALIGN(fc_baserel->reltarget->width) +
			MAXALIGN(SizeofHeapTupleHeader);
		fc_ntuples = clamp_row_est((double) fc_stat_buf.st_size /
								(double) fc_tuple_width);
	}
	fc_fdw_private->ntuples = fc_ntuples;

	/*
	 * 现在估计在应用基表限制信息条件后扫描返回的行数。
	 */
	fc_nrows = fc_ntuples *
		clauselist_selectivity(fc_root,
							   fc_baserel->baserestrictinfo,
							   0,
							   JOIN_INNER,
							   NULL);

	fc_nrows = clamp_row_est(fc_nrows);

	/* 保存输出行的估计供规划者使用 */
	fc_baserel->rows = fc_nrows;
}

/*
 * 估计扫描外部表的成本。
 *
 * 结果返回在 *startup_cost 和 *total_cost 中。
 */
static void fc_estimate_costs(PlannerInfo *fc_root, RelOptInfo *fc_baserel,
			   FileFdwPlanState *fc_fdw_private,
			   Cost *fc_startup_cost, Cost *fc_total_cost)
{
	BlockNumber fc_pages = fc_fdw_private->pages;
	double		fc_ntuples = fc_fdw_private->ntuples;
	Cost		fc_run_cost = 0;
	Cost		fc_cpu_per_tuple;

	/*
	 * 我们几乎以与 cost_seqscan() 相同的方式估计成本，因此假设 I/O 成本等同于同一大小的常规表文件。
	 * 然而，我们将每元组的 CPU 成本视为 seqscan 的 10 倍，以考虑解析记录的成本。
	 *
	 * 在程序源的情况下，这一计算甚至与现实更加脱节，但我们没有好的替代方案；而且目前我们生成的数字无论如何似乎都不是很重要，因为该关系只有一条访问路径。
	 */
	fc_run_cost += seq_page_cost * fc_pages;

	*fc_startup_cost = fc_baserel->baserestrictcost.startup;
	fc_cpu_per_tuple = cpu_tuple_cost * 10 + fc_baserel->baserestrictcost.per_tuple;
	fc_run_cost += fc_cpu_per_tuple * fc_ntuples;
	*fc_total_cost = *fc_startup_cost + fc_run_cost;
}

/*
 * file_acquire_sample_rows -- 从表中获取随机样本行
 *
 * 选定的行以调用者分配的数组 rows[] 返回，必须至少有 targrows 条目。
 * 选择的实际行数作为函数结果返回。
 * 我们还计算文件中的总行数并将其返回到 *totalrows 中。注意 *totaldeadrows 始终设为 0。
 *
 * 注意，返回的行列表并不总是按文件中的物理位置排序。因此，稍后派生的相关性估计可能是毫无意义的，但这没关系，因为我们目前不使用估计（规划者只关注索引扫描的相关性）。
 */
static int fc_file_acquire_sample_rows(Relation fc_onerel, int fc_elevel,
						 HeapTuple *fc_rows, int fc_targrows,
						 double *fc_totalrows, double *fc_totaldeadrows)
{
	int			fc_numrows = 0;
	double		fc_rowstoskip = -1;	/* -1 意味着尚未设置 */
	ReservoirStateData fc_rstate;
	TupleDesc	fc_tupDesc;
	Datum	   *fc_values;
	bool	   *fc_nulls;
	bool		fc_found;
	char	   *fc_filename;
	bool		fc_is_program;
	List	   *fc_options;
	CopyFromState fc_cstate;
	ErrorContextCallback fc_errcallback;
	MemoryContext fc_oldcontext = CurrentMemoryContext;
	MemoryContext fc_tupcontext;

	Assert(fc_onerel);
	Assert(fc_targrows > 0);

	fc_tupDesc = RelationGetDescr(fc_onerel);
	fc_values = (Datum *) palloc(fc_tupDesc->natts * sizeof(Datum));
	fc_nulls = (bool *) palloc(fc_tupDesc->natts * sizeof(bool));

	/* 获取外部表的选项 */
	fc_fileGetOptions(RelationGetRelid(fc_onerel), &fc_filename, &fc_is_program, &fc_options);

	/*
	 * 从 FDW 选项创建 CopyState。
	 */
	fc_cstate = BeginCopyFrom(NULL, fc_onerel, NULL, fc_filename, fc_is_program, NULL, NIL,
						   fc_options);

	/*
	 * 使用每元组内存上下文以防止在使用 Copy 例程从文件中读取行时内存泄漏。
	 */
	fc_tupcontext = AllocSetContextCreate(CurrentMemoryContext,
									   "file_fdw temporary context",
									   ALLOCSET_DEFAULT_SIZES);

	/* 为采样行做准备 */
	reservoir_init_selection_state(&fc_rstate, fc_targrows);

	/* 设置回调以识别错误行号。 */
	fc_errcallback.callback = CopyFromErrorCallback;
	fc_errcallback.arg = (void *) fc_cstate;
	fc_errcallback.previous = error_context_stack;
	error_context_stack = &fc_errcallback;

	*fc_totalrows = 0;
	*fc_totaldeadrows = 0;
	for (;;)
	{
		/* 检查用户请求的中止或休眠 */
		vacuum_delay_point();

		/* 获取下一行 */
		MemoryContextReset(fc_tupcontext);
		MemoryContextSwitchTo(fc_tupcontext);

		fc_found = NextCopyFrom(fc_cstate, NULL, fc_values, fc_nulls);

		MemoryContextSwitchTo(fc_oldcontext);

		if (!fc_found)
			break;

		/*
		 * 前 targrows 个样本行简单地复制到水库中。然后我们开始替换样本中的元组，直到达到关系的末尾。该算法来自 Jeff Vitter 的论文（更多信息见 commands/analyze.c）。
		 */
		if (fc_numrows < fc_targrows)
		{
			fc_rows[fc_numrows++] = heap_form_tuple(fc_tupDesc, fc_values, fc_nulls);
		}
		else
		{
			/*
			 * Vitter 论文中的 t 是已经处理的记录数。
			 * 如果我们需要计算一个新的 S 值，我们必须使用尚未递增的 totalrows 值作为 t。
			 */
			if (fc_rowstoskip < 0)
				fc_rowstoskip = reservoir_get_next_S(&fc_rstate, *fc_totalrows, fc_targrows);

			if (fc_rowstoskip <= 0)
			{
				/*
				 * 找到合适的元组，因此保存它，随机替换一个旧元组。
				 */
				int			fc_k = (int) (fc_targrows * sampler_random_fract(&fc_rstate.randstate));

				Assert(fc_k >= 0 && fc_k < fc_targrows);
				heap_freetuple(fc_rows[fc_k]);
				fc_rows[fc_k] = heap_form_tuple(fc_tupDesc, fc_values, fc_nulls);
			}

			fc_rowstoskip -= 1;
		}

		*fc_totalrows += 1;
	}

	/* 移除错误回调。 */
	error_context_stack = fc_errcallback.previous;

	/* 清理。 */
	MemoryContextDelete(fc_tupcontext);

	EndCopyFrom(fc_cstate);

	pfree(fc_values);
	pfree(fc_nulls);

	/*
	 * 输出一些有趣的关系信息
	 */
	ereport(fc_elevel,
			(errmsg("\"%s\": file contains %.0f rows; "
					"%d rows in sample",
					RelationGetRelationName(fc_onerel),
					*fc_totalrows, fc_numrows)));

	return fc_numrows;
}
